
/***************************** item_33: 避免遮掩继承而来的名称 **********************************/

// 诸如这般代码中：
#include <iostream>

int x;
void someFunc()
{
    double x;
    std::cin >> x;
}

// 上面这段代码中读取数据的语句指涉的是 local 变量 x，而不是 global 变量 x，因为内层作用域的名称会遮掩（遮蔽）外围作用域的名称。
// 当编译器处于 someFunc 的作用域内并遭遇名称 x 时，它在 local 作用域内查找是否有什么东西带着这个名称。如果找到就不再找其他作用域。本例的 someFunc 的 x 是 double 类型而
// global x 是 int 类型，但那不要紧。C++的名称遮掩规则所做的唯一事情就是：遮掩名称。至于名称是否应和相同或不同的类型，并不重要。本例中一个名为 x 的 double遮掩了一个名为 x 的int。

// 当位于一个 derived class 成员函数内指涉 base class内的某物（也许是个成员函数、typedef、或成员函数）时，编译器可以找出我们所涉的东西，因为 derived classes 继承了声明
// 于 base classes 内的所有东西。实际运作方式是，derived class作用域被嵌套在 base class 作用域内，像这样：
class Base {
    private:
        int x;
    public:
        virtual void mf1() = 0;
        virtual void mf2();
        void mf3();
        // ...
};

class Derived: public Base {
    public:
        virtual void mf1();
        void mf4();
        // ...
};

// 假设 derived class 内的 mf4 实现码部分像这样：
void Derived::mf4()
{
    // ...
    mf2();
    // ...
}

// 当编译器看到这里使用名称 mf2，必须估算它指涉什么东西。编译器的做法是查找各作用域，看看有没有某个名为 mf2 的声明式。首先查找 local 作用域（也就是mf4覆盖的作用域），在那儿
// 没找到任何东西名为 mf2。于是查找其查找其外围作用域，也就是 class Derived 覆盖的作用域。还是没找到任何东西名为 mf2，于是再往外围移动，本例为 base class。在那儿编译器找到
// 一个名为mf2的东西了，于是停止查找。如果Base内还是没有mf2，查找动作便继续下去，首先找内含 Base的那个 namespace（s）的作用域（如果有的话），最后往 global 作用域找去。

// 如果我们重载 mf1 和 mf3，并且添加一个新版 mf3 到 Derived去，这会使整个设计立刻显得疑云重重，如下：
class Base {
    private:
        int x;
    public:
        virtual void mf1() = 0;
        virtual void mf1(int);
        virtual void mf2();
        void mf3();
        void mf3(double);
        // ...
};

class Derived: public Base {
    public:
        virtual void mf1();
        void mf3();
        void mf4();
        // ...
};

//以作用域为基础的 "名称遮掩规则" 并没有改变，因此base class内所有名为 mf1 和 mf3 的函数都被 derived class 内的 mf1 和 mf3 函数遮掩掉了。从名称查找观点来看，Base::mf1
//和 Base::mf3 不再被 Derived 继承！
Derived d;
int x;
// ...
d.mf1();        //没问题，调用 Derived::mf1
d.mf1(x);       //错误！因为 Derived::mf1 遮掩了 Base::mf1
d.mf2();        //没问题，调用 Base::mf2
d.mf3();        //没问题，调用 Derived::mf3
d.mf3(x);       //错误！因为 Derived::mf3 遮掩了 Base::mf3

// 上述规则都适用，即使 base class 和 derived classes 内的函数有不同的参数类型也适用，而且不论函数是 virtual 或 non-virtual 一体适用。这和本条款一开始展示的道理相同，
// 当时函数 someFunc 内的 double x 遮掩了 global作用域内的 int x，如今 Derived 内的函数 mf3 遮掩了一个名为 mf3 但类型不同的 Base 函数。

// 这些行为背后的基本理由是为了防止你在程序库或应用框架内建立新的 derived class 时附带地从疏远的 base classes 继承重载函数。不幸的是你通常会想继承重载函数。实际上如果你正在
// 使用 public 继承而又不继承那些重载函数，就是违反 base 和 derived classes 之间的 is-a 关系，is-a 是 public 继承的基石。因此你几乎总会想要推翻C++对 "继承而来的名称" 
// 的缺省遮掩行为。

// 你可以使用 using 声明式达成目标：
class Base {
    private:
        int x;
    public:
        virtual void mf1() = 0;
        virtual void mf1(int);
        virtual void mf2();
        void mf3();
        void mf3(double);
        // ...
};

class Derived: public Base {
    public:
        using Base::mf1;        //让 Base class 内名为 mf1 和 mf3 的所有东西在 Derived 作用域内都可见（并且public）
        using Base::mf3;
        virtual void mf1();
        void mf3();
        void mf4();
        // ...
};

Derived d;
int x;
// ...
d.mf1();        //仍然没问题，仍然调用 Derived::mf1
d.mf1(x);       //现在没有问题了，调用 Base::mf1
d.mf2();        //仍然没问题，仍然调用 Base::mf2
d.mf3();        //没问题，调用 Derived::mf3
d.mf3(x);       //现在没问题了，调用 Base::mf3

// 这意味如果你继承 base class 并加上重载函数，而你又希望重新定义或覆写（推翻）其中一部分，那么你必须为那些原本会被遮掩的每个名称引入一个 using 声明式，否则某些希望继承的名称
// 会被遮掩。

// 当 Derived 以 private 形式继承 Base，而 Derived 唯一想继承的 mf1 是那个无参数版本。using 声明式在这里派不上用场，因为 using 声明式会令继承而来的某给定名称之所有同名
// 函数在 derived class 中都可见。因此我们需要不同的技术，即一个简单的转交函数：
class Base {
    public:
        virtual void mf1() = 0;
        virtual void mf1(int);
        // ...                  //与前同
};
class Derived: private Base {
    public:
        virtual void mf1()      //转交函数
        {
            Base::mf1();        //暗自成为 inline
        }
        // ...
};
// ...
Derived d;
int x;
d.mf1();            //很好，调用的是 Derived::mf1
d.mf1(x);           //错误！Base::mf1()被遮掩了

// inline 转交函数的另一个用途是为那些不支持 using 声明式的老旧编译器另辟一条新路，将继承而得的名称汇入 derived class 作用域内。

// 总结：
// 1. derived classes 内的名称会遮掩 base classes 内的名称。在 public 继承下从来没有人希望如此。
// 2. 为了让被遮掩的名称再见天日，可使用 using 声明式或转交函数。